#pragma once

#include <string>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Err.hpp"
#include "log.hpp"


static const int default_fd = -1;
static const int gbacklog = 32;

class Sock
{
public:
    Sock()
        : _sock(default_fd)
    {}

    void Socket()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if(_sock == -1)
        {
            LogMessage(Fatal, "Socket create error, code: %d, reason: %s", errno, strerror(errno));
            exit(SOCK_ERR);
        }
    }

    void Bind(const uint16_t& port)
    {
        struct sockaddr_in local;
        socklen_t len = sizeof(local);
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(port);

        if(bind(_sock, (struct sockaddr*)&local, len) < 0)
        {
            LogMessage(Fatal, "Bind error, code: %d, reason: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
    }

    void Listen()
    {
        if(listen(_sock, gbacklog) < 0)
        {
            LogMessage(Fatal, "Listen error, code: %d, reason: %s", errno, strerror(errno));
            exit(SOCK_ERR);
        }
    }

    int Accept(std::string* client_ip, uint16_t* client_port)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        memset(&client, 0, sizeof(client));
        
        int retFD = accept(_sock, (struct sockaddr*)&client, &len);
        if(retFD < 0)
        {
            LogMessage(Warring, "Accpet error, code: %d, reason: %s", errno, strerror(errno));
        }
        else
        {
            *client_ip = inet_ntoa(client.sin_addr);
            *client_port = ntohs(client.sin_port);
        }

        return retFD;
    }

    int Connect(const std::string& client_ip, const uint16_t& client_port)
    {
        struct sockaddr_in server;
        socklen_t len = sizeof(server);
        memset(&server, 0, sizeof(server));

        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(client_ip.c_str());
        server.sin_port = htons(client_port);

        return connect(_sock, (struct sockaddr*)&server, len);
    }

    int GetFd()
    {
        return _sock;
    }

    ~Sock()
    {
        if(_sock != default_fd)
            close(_sock);
    }
private:
    int _sock;
};